package com.demo.paypalmanagement.business.impl;

import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.JSONPath;
import com.demo.paypalmanagement.business.OrderBusinessService;
import com.demo.paypalmanagement.common.*;
import com.demo.paypalmanagement.config.response.BizException;
import com.demo.paypalmanagement.model.entity.Order;
import com.demo.paypalmanagement.model.entity.OrderItems;
import com.demo.paypalmanagement.model.entity.Products;
import com.demo.paypalmanagement.model.entity.Users;
import com.demo.paypalmanagement.model.vo.*;
import com.demo.paypalmanagement.service.IOrderItemsService;
import com.demo.paypalmanagement.service.IOrderService;
import com.demo.paypalmanagement.service.IProductsService;
import com.demo.paypalmanagement.service.IUsersService;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.math.BigDecimal;
import java.net.*;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Service
public class OrderBusinessImpl implements OrderBusinessService {

    private static final Logger log = LoggerFactory.getLogger(OrderBusinessImpl.class);
    @Autowired
    private PathBean pathBean ;

    @Autowired
    private IUsersService usersService;

    @Autowired
    private PayPalAccessToken paypalAccessToken ;

    @Autowired
    private IOrderService orderService ;

    @Autowired
    private IOrderItemsService orderItemsService ;

    @Autowired
    private IProductsService productsService ;

    @Value("${paypal.base-url}")
    private String baseUrl;

    @Autowired
    private RedisTemplate<String , String> redisTemplate;
    @Autowired
    private CommonUtils commonUtils;

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public String payOrder(PayVO vo) throws IOException {
        vo.setUserId(commonUtils.getUserId());
        // 校验
        if (ObjectUtils.isEmpty(vo) || ObjectUtils.isEmpty(vo.getUserId())) {
            throw new BizException(ResultMsg.OBJECT_IS_EMPTY.getMsg());
        }
        validateOrder(vo);
        //查询用户
        Users user = usersService.getById(vo.getUserId());
        if (ObjectUtils.isEmpty(user)) {
            throw new BizException(ResultMsg.USER_IS_NOT_FOUND.getMsg());
        }
        //查询商品
        List<Long> proIdList = vo.getVoList().stream().map(ProductsVO::getId).collect(Collectors.toList());
        List<Products> productsList = productsService.listByIds(proIdList);
        if (CollectionUtils.isEmpty(productsList)) {
            throw new BizException(ResultMsg.DATA_IS_NULL.getMsg());
        }
        //计算订单总金额
        Map<Long, Products> priceMap = productsList.stream().collect(Collectors.toMap(Products::getId, Function.identity(), (k1, k2) -> k1));
        BigDecimal orderTotalPrice = new BigDecimal(0);
        for (ProductsVO voe : vo.getVoList()) {
            Products entity = priceMap.get(voe.getId());
            if (ObjectUtils.isEmpty(entity)) {
                continue;
            }
            int qt = entity.getStockQuantity()-voe.getNum();
            if (qt <= 0) {
                throw new BindException(ResultMsg.BUY_NUM_GT_QT.getMsg());
            }
            entity.setStockQuantity(entity.getStockQuantity()-voe.getNum());
            BigDecimal totalPrice = entity.getPrice();
            voe.setPrice(totalPrice);
            voe.setName(entity.getName());
            totalPrice = totalPrice.multiply(new BigDecimal(voe.getNum()));
            orderTotalPrice = orderTotalPrice.add(totalPrice);
        }
        Order order = new Order();
        order.setPayOrderNo(IdUtil.getSnowflake().nextId());
        order.setUserId(vo.getUserId());
        order.setPayChannel("paypal");
        order.setTotalAmount(orderTotalPrice.intValue());
        order.setRealAmount(orderTotalPrice.intValue());
        order.setCreater(vo.getUserId().longValue());
        order.setUpdater(vo.getUserId().longValue());
        order.setProvince(vo.getState());
        order.setCity(vo.getCity());
        order.setStreet(vo.getStreet());
        order.setPostCode(vo.getZipCode());
        order.setMobile(vo.getMobile());
        order.setContact(vo.getContact());
        order.setStatus(1);
        orderService.save(order);
        //生成订单项
        List<OrderItems> orderItemList = buildOrderItems(order,vo.getVoList());
        orderItemsService.saveBatch(orderItemList);
        // 下单
        String res = payOrder(order, user);
        PaypalVO paypalVO = JSON.parseObject(res, PaypalVO.class);
        productsService.saveOrUpdateBatch(priceMap.values());
        String linkStr = null;
        for (PayLinksVO link : paypalVO.getLinks()) {
            if (!link.getRel().equals("approve")){
                continue;
            }
            linkStr = link.getHref();
        }

        Pattern pattern = Pattern.compile("token=([^&]*)");
        Matcher matcher = pattern.matcher(StringUtils.isBlank(linkStr)?"":linkStr);

        if (matcher.find()) {
            String token = matcher.group(1);
            order.setToken(token);
            orderService.updateById(order);
            redisTemplate.opsForValue().set(RedisConstants.ORDER_KEY_PREFIX+token,token+":"+order.getPayOrderNo(),RedisConstants.ORDER_EXPIRE_TIME, TimeUnit.SECONDS);
        }
        return linkStr;
    }

    @Override
    public Boolean confirmOrder(PaypalVO data) throws IOException {
        // 传入订单ID作为路径参数
        String api = baseUrl + String.format("/checkout/orders/%1s/capture",data.getToken());
        URL url = new URL(api);
        HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
        httpConn.setRequestMethod("POST");

        httpConn.setRequestProperty("Content-Type", "application/json");
        httpConn.setRequestProperty("PayPal-Request-Id", UUID.randomUUID().toString());
        // 设置 accessToken
        httpConn.setRequestProperty("Authorization", "Bearer "+paypalAccessToken.getAccessToken().getAccessToken());

        InputStream responseStream = httpConn.getResponseCode() / 100 == 2
                ? httpConn.getInputStream()
                : httpConn.getErrorStream();
        Scanner s = new Scanner(responseStream).useDelimiter("\\A");
        String response = s.hasNext() ? s.next() : "";
        try{
            Object result = JSONPath.eval(JSON.parse(response), "$.purchase_units[0].payments.captures[0].id");
            OrderVO orderVO = new OrderVO();
            orderVO.setToken(data.getToken());
            Order one = orderService.getOne(BuildQuery.buildOrderQuery(orderVO));
            if (ObjectUtils.isEmpty(one)) {
                return false;
            }
            one.setCaptureId(result.toString());
            one.setStatus(2);
            orderService.updateById(one);
            redisTemplate.delete(RedisConstants.ORDER_KEY_PREFIX+one.getToken());
        }catch (Exception e){
            log.error(e.getMessage());
            return false;
        }
        return true;
    }

    @Override
    public Boolean refund(OrderVO vo) throws IOException {
        if (ObjectUtils.isEmpty(vo) || ObjectUtils.isEmpty(vo.getPayOrderNo())) {
            return false;
        }
        Order order = orderService.getOne(BuildQuery.buildOrderQuery(vo));
        if (ObjectUtils.isEmpty(order) || !order.getStatus().equals(6)) {
            log.error("未申请退款，直接操作，退款应该交给后台，6为申请退款");
            return false;
        }
        refund(order);
        return orderService.updateById(order);
    }

    private List<OrderItems> buildOrderItems(Order order, List<ProductsVO> voList) {
        List<OrderItems> orderItemList = new ArrayList<>();
        for (ProductsVO vo : voList) {
            OrderItems orderItems = new OrderItems();
            orderItems.setProductId(vo.getId());
            orderItems.setProductName(vo.getName());
            orderItems.setQuantity(vo.getNum());
            orderItems.setOrderId(order.getOrderId());
            orderItems.setTotalPrice(vo.getPrice().multiply(new BigDecimal(vo.getNum())));
            orderItemList.add(orderItems);
        }
        return orderItemList;
    }

    /**
     * 校验订单，不符合将校验信息返回给前端
     * @param voReq
     */
    void validateOrder(PayVO voReq) {

        if (ObjectUtils.isEmpty(voReq.getVoList())) {
            throw new BizException(ResultMsg.QUANTITY_LAST_ONE.getMsg());
        }
        if (ObjectUtils.isEmpty(voReq.getState())) {
            throw new BizException(ResultMsg.ADDRESS_IS_NOT_COMPLETE.getMsg());
        }
        if (ObjectUtils.isEmpty(voReq.getCity())) {
            throw new BizException(ResultMsg.ADDRESS_IS_NOT_COMPLETE.getMsg());
        }
        if (ObjectUtils.isEmpty(voReq.getStreet())) {
            throw new BizException(ResultMsg.ADDRESS_IS_NOT_COMPLETE.getMsg());
        }
        if (ObjectUtils.isEmpty(voReq.getZipCode())) {
            throw new BizException(ResultMsg.ADDRESS_IS_NOT_COMPLETE.getMsg());
        }
        if (StringUtils.isBlank(voReq.getContact())) {
            throw new BizException(ResultMsg.NAME_IS_EMPTY.getMsg());
        }


        for (ProductsVO vo : voReq.getVoList()) {
            if (ObjectUtils.isEmpty(vo.getId())) {
                throw new BizException(ResultMsg.OBJECT_IS_EMPTY.getMsg());
            }
            if (ObjectUtils.isEmpty(vo.getNum())) {
                throw new BizException(ResultMsg.NUM_IS_EMPTY.getMsg());
            }
        }

    }

    String payOrder(Order order, Users users) throws IOException {
        URL url = new URL(baseUrl + pathBean.getPath(Constants.PAY_ORDER.getConstant()));
        HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
        httpConn.setRequestMethod("POST");

        httpConn.setRequestProperty("Content-Type", "application/json");
        httpConn.setRequestProperty("PayPal-Request-Id", UUID.randomUUID().toString());
        // 设置 accessToken
        httpConn.setRequestProperty("Authorization", "Bearer " + paypalAccessToken.getAccessToken().getAccessToken());

        // 金额
        JSONObject amount = new JSONObject();
        amount.put("currency_code", "USD");
        amount.put("value", order.getTotalAmount());

        JSONObject address = new JSONObject();
        address.put("address_line_1", order.getStreet());
        address.put("address_line_2", order.getStreet());
        address.put("admin_area_1", order.getProvince());
        address.put("admin_area_2", order.getCity());
        address.put("postal_code", order.getPostCode());
        address.put("country_code", "CN");

        // 客户姓名
        JSONObject name = new JSONObject();
        name.put("full_name", order.getContact());
        // 客户信息机地址
        JSONObject shipping = new JSONObject();
        shipping.put("name", name);
        shipping.put("address", address);

        // 购买人信息，数组，最多可以同时传10个
        JSONObject purchaseUnit = new JSONObject();
        purchaseUnit.put("reference_id", UUID.randomUUID().toString());
        purchaseUnit.put("custom_id", UUID.randomUUID().toString());
        purchaseUnit.put("amount", amount);
        purchaseUnit.put("description", "支付");
        purchaseUnit.put("shipping", shipping);
        JSONArray puprchase_units = new JSONArray();
        puprchase_units.add(purchaseUnit);
        // 订单上下文信息，取消地址、返回地址设置
        JSONObject applicationContext = new JSONObject();
        applicationContext.put("cancel_url", "http://localhost:80/order/paySuccess");
        applicationContext.put("return_url", "http://localhost:80/order/paySuccess");
        JSONObject json = new JSONObject();
        json.put("intent", "CAPTURE");// 用户付款了，商户立即收款
        json.put("purchase_units", puprchase_units);
        json.put("application_context", applicationContext);
        httpConn.setDoOutput(true);
        OutputStreamWriter writer = new OutputStreamWriter(httpConn.getOutputStream());
        writer.write(json.toJSONString());
        writer.flush();
        writer.close();
        httpConn.getOutputStream().close();

        InputStream responseStream = httpConn.getResponseCode() / 100 == 2
                ? httpConn.getInputStream()
                : httpConn.getErrorStream();
        Scanner s = new Scanner(responseStream).useDelimiter("\\A");
        String response = s.hasNext() ? s.next() : "";
        return response;
    }

    public String refund(Order order) throws IOException {

            // 传入订单ID作为路径参数
            String api = String.format(baseUrl+"/payments/captures/%1s/refund",order.getCaptureId());
            URL url = new URL(api);
            HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
            httpConn.setRequestMethod("POST");

            httpConn.setRequestProperty("Content-Type", "application/json");
            httpConn.setRequestProperty("Prefer", "return=representation");
            httpConn.setRequestProperty("PayPal-Request-Id", UUID.randomUUID().toString());
            // 设置 accessToken
            httpConn.setRequestProperty("Authorization", "Bearer "+paypalAccessToken.getAccessToken().getAccessToken());
            httpConn.setDoOutput(true);

            JSONObject json = new JSONObject();
            // 传入退款金额
            JSONObject amount = new JSONObject();
            amount.put("value",order.getTotalAmount());
            amount.put("currency_code","USD");
            json.put("amount",amount);
            OutputStreamWriter writer = new OutputStreamWriter(httpConn.getOutputStream());
            writer.write(json.toJSONString());
            writer.flush();
            writer.close();
            httpConn.getOutputStream().close();

            InputStream responseStream = httpConn.getResponseCode() / 100 == 2
                    ? httpConn.getInputStream()
                    : httpConn.getErrorStream();
            Scanner s = new Scanner(responseStream).useDelimiter("\\A");
            String response = s.hasNext() ? s.next() : "";
            if (httpConn.getResponseCode() / 100 == 2) {
                order.setStatus(3);
            }

        return response;
    }
}
